'use strict'
const fse = require('fs-extra')
const ejs = require('ejs')
const glob = require('glob')
const Command = require('@medlog-cli-dev/command')
const logger = require('@medlog-cli-dev/log')
const {
  isDirEmpty,
  isValidName,
  spinnerStart,
  sleep,
  execAsync,
} = require('@medlog-cli-dev/utils')
const inquirer = require('inquirer')
const semver = require('semver')
const getProjectTemplate = require('./getProjectTemplate')
const Package = require('@medlog-cli-dev/package')
const userHome = require('user-home')

const TYPE_PROJECT = 'project'
const TYPE_COMPONENT = 'component'
const TEMPLATE_TYPE_NORMAL = 'normal'
const TEMPLATE_TYPE_CUSTOM = 'custom'

const WHITE_COMMAND = ['npm', 'cnpm']

// templateInfo 《Object》: 用户选中的模版信息
// template [List]       : 数据库中的所有包信息
// templateNpm  《Object》: Package对象，用来下载模版
// projectInfo  《Object》: 用户选择的交互信息
class InitCommand extends Command {
  init() {
    this.projectName = this._argv[0] || ''
    this.force = !!this._cmd.force
  }
  async exec() {
    try {
      // 1.准备阶段
      const projectInfo = await this._prepare()
      if (projectInfo) {
        // 2.下载模版
        this.projectInfo = projectInfo
        await this._downloadTemplate()
        // //3.安装模板
        await this._installTemplate()
      }
    } catch (error) {
      if (process.env.LOG_LEVEL === 'verbose') {
      }
    }
  }

  async _installTemplate() {
    if (this.templateInfo) {
      if (!this.templateInfo.type) {
        this.templateInfo.type = TEMPLATE_TYPE_NORMAL
      }
      if (this.templateInfo.type === TEMPLATE_TYPE_NORMAL) {
        //标准安装
        await this._installNormalTemplate()
      } else if (this.templateInfo.type === TEMPLATE_TYPE_CUSTOM) {
        //自定义安装
        await this._installCustomTemplate()
      } else {
        throw new Error('项目模板信息无法识别！')
      }
    } else {
      throw new Error('项目模板信息不存在！')
    }
  }

  async _installNormalTemplate() {
    const spinner = spinnerStart('正在安装模板...')
    await sleep()
    try {
      const templatePath = path.resolve(
        this.templateNpm.cacheFilePath,
        'template'
      )
      const targetPath = process.cwd()
      fse.ensureDirSync(templatePath) //确保使用前目录存在
      fse.ensureDirSync(targetPath)
      fse.copySync(templatePath, targetPath)
    } catch (error) {
      throw error
    } finally {
      spinner.stop(true)
      logger.success('模板安装成功')
    }

    // 渲染模版引擎ejs
    const templateIgnore = this.templateInfo.ignore || []
    const ignore = ['**/node_modules/**', ...templateIgnore]
    // const ignore = ['node_modules/**', 'public/**']
    await this._ejsRender({ ignore })

    // 安装依赖
    const { installCommand, startCommand } = this.templateInfo
    // 依赖安装
    await this._execCommand(installCommand, '依赖过程安装失败！')
    // 启动命令执行
    await this._execCommand(startCommand, '启动命令执行失败失败！')
  }
  _installCustomTemplate() {}
  // 获取服务器上的模版数据并存放于template中
  async _prepare() {
    //0.从服务器拿到模版下载的数据
    const template = await getProjectTemplate()
    if (!template || template.length === 0) {
      throw new Error('项目模板不存在！')
    }
    this.template = template

    // 获取项目路径，区别于__dirname，
    const localPath = process.cwd()
    if (!isDirEmpty(localPath)) {
      let ifContinue = false
      if (!this.force) {
        // 询问是否创建
        ifContinue = (
          await inquirer.prompt([
            {
              type: 'confirm',
              name: 'ifContinue',
              message: '当前文件夹不为空，是否继续创建项目？',
            },
          ])
        ).ifContinue
        if (!ifContinue) {
          return
        }
      }
      //2.是否启动强制更新
      if (ifContinue || this.force) {
        // 清空前给用户做二次确认
        const { confirmDelete } = await inquirer.prompt([
          {
            type: 'confirm',
            name: 'confirmDelete',
            default: false,
            message: '是否确认清空当前目录下的文件？',
          },
        ])
        if (confirmDelete) {
          fse.emptyDirSync(localPath)
        }
      }
    }
    return this._getProjectInfo()
  }
  // 获取交互结果
  async _getProjectInfo() {
    let projectInfo = {}

    //1.选取创建项目或组件
    const { type } = await inquirer.prompt({
      type: 'list',
      name: 'type',
      message: '请选择初始化类型',
      default: TYPE_PROJECT,
      choices: [
        {
          name: '项目',
          value: TYPE_PROJECT,
        },
        {
          name: '组件',
          value: TYPE_COMPONENT,
        },
      ],
    })
    // 根据选择的类型对template进行处理,tag是后端返回的一个数组
    this.template = this.template.filter((t) => t.tag.includes(type))

    const title = type === TYPE_PROJECT ? '项目' : '组件'
    let promptArray = [
      {
        type: 'input',
        name: 'projectVersion',
        default: '1.0.0',
        message: `请输入${title}版本号`,
        validate: function (v) {
          const done = this.async()
          // Do async stuff
          setTimeout(function () {
            if (!!!semver.valid(v)) {
              done(`请输入合法的${title}版本号`)
              return
            }
            done(null, true)
          }, 0)
        },
        filter: function (v) {
          if (semver.valid(v)) {
            return semver.valid(v)
          } else {
            return v
          }
        },
      },
      {
        type: 'list',
        name: 'projectTemplate',
        message: `请选择${title}模板`,
        choices: this._createTemplateChoice(),
      },
    ]
    //  用户直接在命令中输入了项目名
    if (!isValidName(this.projectName)) {
      promptArray.unshift({
        type: 'input',
        name: 'projectName',
        default: '',
        message: `请输入${title}名称`,
        validate: function (v) {
          const done = this.async()
          // Do async stuff
          setTimeout(function () {
            if (!isValidName(v)) {
              done(`请输入合法${title}的名称`)
              return
            }
            done(null, true)
          }, 0)
          // 规则一：输入的首字符为英文字符
          // 规则二：尾字符必须为英文或数字
          // 规则三：字符仅允许-和_两种
          // \w=a-zA_Z0-9_
        },
      })
    } else {
      projectInfo.projectName = this.projectName
    }
    if (type === TYPE_PROJECT) {
      const project = await inquirer.prompt(promptArray)
      projectInfo = {
        ...projectInfo,
        type,
        ...project,
      }
    } else if (type === TYPE_COMPONENT) {
      // 获取组件的基本信息
      const descriptionPrompt = {
        type: 'input',
        name: 'componentDescription',
        message: '请输入组件描述信息',
        default: '',
        validate: function (v) {
          const done = this.async()
          setTimeout(() => {
            if (!v) {
              done('请输入组件描述信息')
              return
            }
            done(null, true)
          }, 0)
        },
      }
      promptArray.push(descriptionPrompt)
      const component = await inquirer.prompt(promptArray)
      projectInfo = {
        ...projectInfo,
        type,
        ...component,
      }
    }

    // 处理key和package.json中一致
    if (projectInfo.projectName) {
      projectInfo.name = projectInfo.projectName
      // 将AbcEfg => abc-efg
      projectInfo.className = require('kebab-case')(
        projectInfo.projectName
      ).replace(/^-/, '')
    }
    if (projectInfo.projectVersion) {
      projectInfo.version = projectInfo.projectVersion
    }
    if (projectInfo.componentDescription) {
      projectInfo.description = projectInfo.componentDescription
    }
    // console.log('projectInfo', projectInfo)
    return projectInfo
  }

  // 从服务器拿到模版数据，让用户手动选择
  _createTemplateChoice() {
    return this.template.map((item) => ({
      value: item.npmName,
      name: item.name,
    }))
  }

  // 获取模版
  async _downloadTemplate() {
    // 前置工作：通过项目模板API获取模板信息
    // 1.1通过egg.js搭建一套后端系统
    // 1.2通过npm存储项目模板
    // 1.3将项目模板信息存储到mongodb数据库中
    // 1.4通过egg.js获取mongodb中的数据并且通过api返回
    const { projectTemplate } = this.projectInfo
    const findNpm = this.template.find(
      (item) => item.npmName === projectTemplate
    )
    this.templateInfo = findNpm
    const targetPath = path.resolve(userHome, '.medlog-cli-dev', 'template')
    const storeDir = path.resolve(
      userHome,
      '.medlog-cli-dev',
      'template',
      'node_modules'
    )
    const { npmName, version } = findNpm
    const pkg = new Package({
      targetPath,
      storeDir,
      packageName: npmName,
      packageVersion: version,
    })
    console.log('pkg', pkg)
    if (!(await pkg.exists())) {
      const spinner = spinnerStart('正在安装模版')
      await sleep()
      try {
        await pkg.install()
      } catch (error) {
        throw error
      } finally {
        spinner.stop(true)
        if (await pkg.exists()) {
          logger.success('下载模板成功！')
        }
      }
    } else {
      const spinner = spinnerStart('正在更新模版')
      await sleep()
      try {
        await pkg.update()
      } catch (error) {
        throw error
      } finally {
        spinner.stop(true)
        if (await pkg.exists()) {
          logger.success('更新模版成功')
        }
      }
    }
    this.templateNpm = pkg
  }

  _checkCommand(cmd) {
    return WHITE_COMMAND.includes(cmd) ? cmd : 'npm'
  }
  async _execCommand(command, errMsg) {
    let ret
    if (command) {
      const cmdArray = command.split(' ')
      const cmd = cmdArray[0]
      const args = cmdArray.slice(1)
      ret = await execAsync(cmd, args, {
        stdio: 'inherit',
        cwd: process.cwd(),
      })
      if (ret !== 0) {
        throw new Error(errMsg)
      }
      return ret
    }
  }

  async _ejsRender(options) {
    const dir = process.cwd()
    const projectInfo = this.projectInfo
    // 拿到文件信息
    return new Promise((resolve, reject) => {
      require('glob')(
        '**',
        {
          cwd: dir,
          ignore: options.ignore || '',
          nodir: true,
        },
        (err, files) => {
          if (err) {
            reject(err)
          }
          // 对所有过滤的文件进行渲染
          Promise.all(
            files.map((file) => {
              const filePath = path.join(dir, file)
              return new Promise((resolve1, reject1) => {
                ejs.renderFile(filePath, projectInfo, {}, (err, result) => {
                  if (err) {
                    reject1(err)
                  }
                  fse.writeFileSync(filePath, result)
                  resolve1(result)
                })
              })
            })
          )
            .then(() => {
              resolve()
            })
            .catch((err) => {
              reject(err)
            })
        }
      )
    })
  }
}
function init(argv) {
  return new InitCommand(argv)
}
module.exports = init
module.exports.InitCommand = InitCommand
